#!/usr/bin/perl -w
# Bug reports and comments to Igor.Ermolaev@intel.com
use strict;
no warnings 'portable';

use constant { FALSE=>0, TRUE=>-1 };
use constant { RELAXED=>0, STRICT=>1, CIRCLE=>2, CIRCLE_STRICT=>3, OUTSIDE=>4, OUTSIDE_STRICT=>5 };

my @blocks = ();
my @info   = ();
my %funcs  = ();
my $mode   = STRICT;
for (@ARGV)
{
   if( $_ eq '-r' )
   {
      $mode = RELAXED;
   }
   elsif( $_ eq '-s' )
   {
      $mode = STRICT;
   }
   elsif( /^(\d+)(\:(\d+))?/o )
   {
      push @blocks, { addr=>$1, size=>$3 } ;
   }
   elsif( /^(.+?)(\:(\d+))?(\:(\d+))?$/o )
   {
      push @info, { first=>undef, use_next=>FALSE, last=>undef, loops=>defined $3 ? int $3 : 0, calls=>defined $5 ? int $5 : 0, loops_seen=>0, entries_seen=>0, exits_seen=>0, calls_seen=>0 };
      $funcs{$1} = $#info;
   }
}
if( exists $ENV{TRACING_SDK_GET_RANGES_MODE} )
{
   if(    $ENV{TRACING_SDK_GET_RANGES_MODE} eq 'relaxed'         ) { $mode = RELAXED;        }
   elsif( $ENV{TRACING_SDK_GET_RANGES_MODE} eq 'strict'          ) { $mode = STRICT;         }
   elsif( $ENV{TRACING_SDK_GET_RANGES_MODE} eq 'circle'          ) { $mode = CIRCLE;         }
   elsif( $ENV{TRACING_SDK_GET_RANGES_MODE} eq 'circle+relaxed'  ) { $mode = CIRCLE;         }
   elsif( $ENV{TRACING_SDK_GET_RANGES_MODE} eq 'circle+strict'   ) { $mode = CIRCLE_STRICT;  }
   elsif( $ENV{TRACING_SDK_GET_RANGES_MODE} eq 'outside'         ) { $mode = OUTSIDE;        }
   elsif( $ENV{TRACING_SDK_GET_RANGES_MODE} eq 'outside+relaxed' ) { $mode = OUTSIDE;        }
   elsif( $ENV{TRACING_SDK_GET_RANGES_MODE} eq 'outside+strict'  ) { $mode = OUTSIDE_STRICT; }
}

my $func_idx = undef;
my $prlg_lst = undef;
my $loop_fst = undef;
my $loop_lst = undef;
my $eplg_fst = undef;
my $loop_ofs = 0;
my $eplg_ofs = 0;
my $cur_addr = undef;
my $cur_exit = undef;
my $blk_cnt  = 0;
my $jmp_addr = undef;
my $jmp_tgt  = undef;
my $skp_addr = undef;
my $skp_tgt  = undef;
my $skp_aye  = undef;
my $ret_sptd = FALSE;
my $ret_wntd = TRUE;
my $omp_sptd = FALSE;


use constant {
   FLOW_INSTR=>qr/^(j[\w,]+)\s+\$?(0x)?([[:xdigit:]]+)/io
};


while( <STDIN> ) 
{ 
   if( /^(0x)?([[:xdigit:]]+)\s*\<([^\>\+]+)(\+(0x)?[[:xdigit:]]+)?\>\:/o )
   {
      if( defined $funcs{$3} )
      {
         $func_idx = $funcs{$3};
         if( !defined $info[$func_idx]{first} || $info[$func_idx]{first} != hex $2 )
         {
            $info[$func_idx]{first} = hex $2;
            $info[$func_idx]{entries_seen}++;
         }
      }
   }
   elsif( /^\s*(.+)\s*\(.*\)\s*\:\s*$/o )
   {
      if( defined $funcs{$1} )
      {
         $func_idx = $funcs{$1};
         if( !defined $info[$func_idx]{first} )
         {
            $info[$func_idx]{use_next} = TRUE;
         }
      }
      my   $lfn = $1;
      $omp_sptd = TRUE if $lfn =~ /^(L_.+__par_[^\d]+)\d.*$/io;
   }
   elsif( /^\s*(0x)?([[:xdigit:]]+)\:\s*([[:xdigit:]]+\s+)*(.+)$/o )
   {
      $cur_addr = hex $2;
      my $instr = $4;
      if( !defined $prlg_lst )
      {
         if( $instr =~ FLOW_INSTR )
         {
            my $tgt_addr = hex $3;
            if( $tgt_addr > $cur_addr )
            {
               if( $1 =~ /jmp[\w,]*/io )
               {
                  $skp_aye  = $tgt_addr if !defined $skp_aye || $tgt_addr > $skp_aye;
               }
               elsif( !defined $skp_tgt || $skp_tgt <= $cur_addr )
               {
                  $skp_addr = $cur_addr;
                  $skp_tgt  = $tgt_addr;
                  $skp_aye  = undef;
               }
            }
         }
      }
      $loop_fst = $cur_addr if defined $prlg_lst && !defined $loop_fst;
      if( defined $loop_lst && !defined $eplg_fst )
      {
         if( ( $mode == STRICT || $mode == CIRCLE_STRICT || $mode == OUTSIDE_STRICT ) && defined $skp_tgt )
         {
            if( $skp_tgt >= $cur_addr && ( !defined $skp_aye || $skp_aye <= $cur_addr ) )
            {
               $prlg_lst = $skp_addr;
               $loop_fst = -1;
               $loop_lst = -1;
            }
         }
         if( ( $mode == STRICT || $mode == CIRCLE_STRICT || $mode == OUTSIDE_STRICT ) && $ret_sptd == TRUE )
         {
            $eplg_fst = $prlg_lst;
            $eplg_ofs = 1;
         }
         elsif( ( $mode == STRICT || $mode == CIRCLE_STRICT || $mode == OUTSIDE_STRICT ) && defined $jmp_tgt && $jmp_tgt > $cur_addr )
         {
            $eplg_fst = $prlg_lst;
            $eplg_ofs = 1;
         }
         elsif( defined $cur_exit )
         {
            $eplg_fst = $cur_exit;
         }
         elsif( $mode == RELAXED && $instr =~ FLOW_INSTR )
         {
            my $tgt_addr = hex $3;
            $eplg_fst = $1 =~ /jmp[\w,]*/io ? $tgt_addr : $cur_addr;
         }
         else
         {
            $eplg_fst = $cur_addr;
         }
      }
      if( defined $loop_fst && !defined $loop_lst )
      {
         if( $#blocks < 0 )
         {
            $blk_cnt++;
         }
         else
         {
            for my $b (@blocks)
            {
               if(  defined $b->{size} && $cur_addr >= $b->{addr} && $cur_addr < $b->{addr} + $b->{size} ||
                   !defined $b->{size} && $cur_addr == $b->{addr} )
               {
                  $blk_cnt++;
                  last;
               }
            }
         }
         if( $instr =~ FLOW_INSTR )
         {
            my $tgt_addr = hex $3;
            if( $tgt_addr > $cur_addr )
            {
                if( defined $jmp_tgt && $jmp_tgt > $cur_addr )
                {
                   $jmp_tgt = $tgt_addr if $tgt_addr > $jmp_tgt;
                }
                else
                {
                   $jmp_addr = $cur_addr;
                   $jmp_tgt  = $tgt_addr;
                }
            }
            if( !( $1 =~ /jmp[\w,]*/io ) )
            {
               if( $tgt_addr > $cur_addr )
               {
                  $cur_exit = $tgt_addr;
               }
               else
               {
                  $cur_exit = undef;
               }
            }
         }
         elsif( !( $instr =~ /\w?mov\w*/io ) )
         {
            $ret_sptd = TRUE if $instr =~ /ret\w*/io;
            $cur_exit = undef;
         }
      }
      if( defined $func_idx )
      {
         if( $info[$func_idx]{use_next} == TRUE )
         {
            $info[$func_idx]{use_next} = FALSE;
            if( !defined $info[$func_idx]{first} || $info[$func_idx]{first} != $cur_addr )
            {
               $info[$func_idx]{first} = $cur_addr;
               $info[$func_idx]{entries_seen}++;
            }
         }
         if( $instr =~ /^ret/io )
         {
            if( $ret_wntd == TRUE )
            {
               if( $mode != RELAXED || $omp_sptd == FALSE )
               {
                  $info[$func_idx]{last} = $cur_addr;
                  $info[$func_idx]{exits_seen}++;
               }
            }
            else
            {
               $ret_wntd = TRUE;
            }
         }
         elsif( $instr =~ /^call/io )
         {
            $ret_wntd = TRUE;
            if( $instr =~ /^call(q)?\s+[[:xdigit:]]+\s*\<([^>]+)\>/io )
            {
               my $tgt = $2;
               if( $mode == RELAXED && $tgt =~ /^__kmpc_(for_static_fini)/io )
               {
                  $ret_wntd = FALSE;
               }
            }
            $info[$func_idx]{calls_seen}++ if $ret_wntd == TRUE;
         }
         elsif( $instr =~ FLOW_INSTR )
         {
            if( hex $3 < $cur_addr && !( $1 =~ /jmp[\w,]*/io ) )
            {
               $info[$func_idx]{loops_seen}++;
            }
         }
      }
   }
   elsif( /^;---/o )
   {
      if( defined $prlg_lst )
      {
         if( defined $cur_addr )
         {
            if( ( $mode == STRICT || $mode == CIRCLE_STRICT || $mode == OUTSIDE_STRICT ) && defined $jmp_tgt && $jmp_tgt > $cur_addr )
            {
                $loop_lst = $jmp_addr;
                $loop_ofs = 1;
            }
            else
            {
                $loop_lst = $cur_addr;
            }
         }
         else
         {
            $loop_lst = -1;
         }
      }
      else
      {
         $prlg_lst = defined $cur_addr ? $cur_addr : -1;
      }
   }
   elsif( /^;===/o )
   {
      if( $blk_cnt )
      {
         if( $mode == OUTSIDE )
         {
            print $eplg_fst == -1 ? $eplg_fst : sprintf '0x%x', $eplg_fst; print ' ';
            print $loop_lst == -1 ? $loop_lst : sprintf '0x%x', $loop_lst; print ' ';
         }
         else
         {
            print $prlg_lst == -1 ? $prlg_lst : sprintf '0x%x', $prlg_lst; print ' ';
            print $loop_fst == -1 ? $loop_fst : sprintf '0x%x', $loop_fst; print ' ';
         }
         if( $mode == CIRCLE || $mode == CIRCLE_STRICT )
         {
            print $loop_fst == -1 ? $loop_fst : sprintf '0x%x', $loop_fst; print ' ';
            print $prlg_lst == -1 ? $prlg_lst : sprintf '0x%x', $prlg_lst; print " 1 1\n"; #print " $loop_ofs $eplg_ofs\n";
         }
         elsif( $mode == OUTSIDE || $mode == OUTSIDE_STRICT )
         {
            print $loop_fst == -1 ? $loop_fst : sprintf '0x%x', $loop_fst; print ' ';
            print $prlg_lst == -1 ? $prlg_lst : sprintf '0x%x', $prlg_lst; print " $loop_ofs $eplg_ofs\n";
         }
         else
         {
            print $loop_lst == -1 ? $loop_lst : sprintf '0x%x', $loop_lst; print ' ';
            print $eplg_fst == -1 ? $eplg_fst : sprintf '0x%x', $eplg_fst; print " $loop_ofs $eplg_ofs\n";
         }
      }
      $prlg_lst = undef;
      $loop_fst = undef;
      $loop_lst = undef;
      $eplg_fst = undef;
      $cur_addr = undef;
      $cur_exit = undef;
      $blk_cnt  = 0;
   }
}
my $status  = 0;
my $simples = 0;
for( @info )
{
   if( !defined $_->{first} )
   {
      $status = -1;
   }
   elsif( $_->{exits_seen} == 0 )
   {
      if( $mode == RELAXED || $mode == CIRCLE || $mode == OUTSIDE )
      {
         $simples++;
         #my $adj = $mode == CIRCLE ? 0 : 1;
         if( defined $skp_addr && $mode != CIRCLE )
         {
            if( $mode == OUTSIDE )
            {
               printf "0x%x 0x%x 0\n", $skp_addr, $_->{first};#printf "0x%x 0x%x %d\n", $skp_addr, $_->{first}, $adj;
            }
            else
            {
               printf "0x%x 0x%x 0\n", $_->{first}, $skp_addr;#printf "0x%x 0x%x %d\n", $_->{first}, $skp_addr, $adj;
            }
         }
         else
         {
            printf "0x%x 0x%x 1\n", $_->{first}, $_->{first};#printf "0x%x 0x%x %d\n", $_->{first}, $_->{first}, $adj;
         }
      }
      else
      {
         $status = 1;
      }
   }
   elsif( $_->{exits_seen} > 1 )
   {
      if( $mode == RELAXED || $mode == CIRCLE || $mode == OUTSIDE )
      {
         $simples++;
         printf "0x%x 0x%x 1\n", $_->{first}, $_->{first};
         #my $adj = $mode == CIRCLE ? 0 : 1;
         #printf "0x%x 0x%x %d\n", $_->{first}, $_->{first}, $adj;
      }
      else
      {
         $status = 2;
      }
   }
   elsif( $_->{loops_seen} > $_->{loops} && ( $mode == STRICT || $mode == CIRCLE_STRICT || $mode == OUTSIDE_STRICT ) )
   {
      $status = 3;
   }
   elsif( $_->{calls_seen} > $_->{calls} && ( $mode == STRICT || $mode == CIRCLE_STRICT || $mode == OUTSIDE_STRICT ) )
   {
      $status = 4;
   }
   else
   {
      $simples++;
      if( $mode == CIRCLE || $mode == CIRCLE_STRICT )
      {
         printf "0x%x 0x%x 1\n", $_->{first}, $_->{first};#printf "0x%x 0x%x 0\n", $_->{first}, $_->{first};
      }
      elsif( $mode == OUTSIDE || $mode == OUTSIDE_STRICT )
      {
         printf "0x%x 0x%x 0\n", $_->{last}, $_->{first};
      }
      else
      {
         printf "0x%x 0x%x 0\n", $_->{first}, $_->{last};
      }
   }
}
exit( $simples > 0 ? 0 : $status );
